
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Advanced Particle Heart Animation</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; }
    </style>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // Set up scene, camera, and renderer
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        renderer.shadowMap.enabled = true;

        // Lighting
        const ambientLight = new THREE.AmbientLight(0x404040, 2); // soft ambient light
        scene.add(ambientLight);

        const pointLight = new THREE.PointLight(0xff0077, 5, 50);
        pointLight.position.set(2, 3, 4);
        pointLight.castShadow = true;
        scene.add(pointLight);

        // Heart Shape Particle System
        const particleCount = 4000;
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(particleCount * 3);

        // Heart-shaped particle positions
        for (let i = 0; i < particleCount; i++) {
            const theta = Math.random() * Math.PI * 2;
            const phi = Math.acos((Math.random() * 2) - 1);
            const r = 1 + (Math.random() * 0.5);

            const x = r * Math.sin(phi) * Math.cos(theta);
            const y = r * Math.sin(phi) * Math.sin(theta);
            const z = r * Math.cos(phi);

            // Only create particles within the heart shape
            if ((x * x + 1.25 * y * y - z * z) < 0.1) {
                positions[i * 3] = x * 1.2;
                positions[i * 3 + 1] = y * 1.2;
                positions[i * 3 + 2] = z * 1.2;
            }
        }

        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

        const material = new THREE.PointsMaterial({
            color: 0xff0077,
            size: 0.05,
            transparent: true,
            opacity: 0.8,
            blending: THREE.AdditiveBlending,
        });

        const heartParticles = new THREE.Points(geometry, material);
        scene.add(heartParticles);

        // Blue Tray Particle System
        const trayGeometry = new THREE.BufferGeometry();
        const trayParticleCount = 2000;
        const trayPositions = new Float32Array(trayParticleCount * 3);

        for (let i = 0; i < trayParticleCount; i++) {
            trayPositions[i * 3] = (Math.random() - 0.5) * 6;
            trayPositions[i * 3 + 1] = -1.5;  // Ground level
            trayPositions[i * 3 + 2] = (Math.random() - 0.5) * 6;
        }

        trayGeometry.setAttribute('position', new THREE.BufferAttribute(trayPositions, 3));
        const trayMaterial = new THREE.PointsMaterial({
            color: 0x00aaff,
            size: 0.08,
            transparent: true,
            opacity: 0.6,
            blending: THREE.AdditiveBlending,
        });

        const trayParticles = new THREE.Points(trayGeometry, trayMaterial);
        scene.add(trayParticles);

        // Animation
        camera.position.z = 6;
        const clock = new THREE.Clock();

        function animate() {
            requestAnimationFrame(animate);

            const elapsedTime = clock.getElapsedTime();

            // Rotate heart particles
            heartParticles.rotation.y = elapsedTime * 0.5;
            heartParticles.rotation.x = Math.sin(elapsedTime * 0.3) * 0.5;

            // Update particle positions for a falling effect
            const positionsArray = geometry.attributes.position.array;
            for (let i = 0; i < particleCount; i++) {
                positionsArray[i * 3 + 1] -= 0.002;  // Gravity effect

                // Reset position when falling past a threshold
                if (positionsArray[i * 3 + 1] < -2) {
                    positionsArray[i * 3 + 1] = 2;
                }
            }
            geometry.attributes.position.needsUpdate = true;

            // Render the scene
            renderer.render(scene, camera);
        }

        animate();

        // Handle window resizing
        window.addEventListener('resize', function () {
            const width = window.innerWidth;
            const height = window.innerHeight;
            renderer.setSize(width, height);
            camera.aspect = width / height;
            camera.updateProjectionMatrix();
        });
    </script>
</body>
</html>
